UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2026 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "httpclient.h" 30 31 #include "../util/socket.h" 32 33 #include <cx/buffer.h> 34 #include <cx/string.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <errno.h> 38 39 static int client_connected(EventHandler *ev, Event *event); 40 static int client_io(EventHandler *ev, Event *event); 41 static int client_finished(EventHandler *ev, Event *event); 42 43 static int client_send_request(HttpClient *client); 44 static int client_send_request_body(HttpClient *client); 45 static int client_read_response_header(HttpClient *client); 46 static int client_read_response_body(HttpClient *client); 47 48 HttpClient* http_client_new(EventHandler *ev) { 49 CxMempool *mp = cxMempoolCreate(32, CX_MEMPOOL_TYPE_PURE); 50 if(!mp) { 51 return NULL; 52 } 53 54 HttpClient *client = malloc(sizeof(HttpClient)); 55 HeaderArray *req_headers = header_array_create(); 56 HeaderArray *resp_headers = header_array_create(); 57 if(!client || !req_headers || !resp_headers) { 58 free(client); 59 header_array_free(req_headers); 60 header_array_free(resp_headers); 61 cxMempoolFree(mp); 62 return NULL; 63 } 64 65 memset(client, 0, sizeof(HttpClient)); 66 client->ev = ev; 67 client->socketfd = -1; 68 client->request_headers = req_headers; 69 client->response_headers = resp_headers; 70 71 client->buffer.maxsize = HTTP_CLIENT_BUFFER_SIZE; 72 client->buffer.inbuf = malloc(HTTP_CLIENT_BUFFER_SIZE); 73 HttpParser *parser = http_parser_new2(1, &client->buffer, resp_headers); 74 if(!parser || !client->buffer.inbuf) { 75 http_client_free(client); 76 return NULL; 77 } 78 client->parser = parser; 79 80 return client; 81 } 82 83 void http_client_free(HttpClient *client) { 84 cxMempoolFree(client->mp); 85 header_array_free(client->request_headers); 86 http_parser_free(client->parser); 87 if(client->stream) { 88 client->stream->st.free(&client->stream->st); 89 } 90 free(client->buffer.inbuf); 91 free(client->addr); 92 free(client->method); 93 free(client->uri); 94 free(client); 95 } 96 97 int http_client_set_addr(HttpClient *client, const struct sockaddr *addr, socklen_t addrlen) { 98 free(client->addr); 99 client->addr = NULL; 100 client->addrlen = 0; 101 102 void *newaddr = malloc(addrlen); 103 if(!newaddr) { 104 return 1; 105 } 106 memcpy(newaddr, addr, addrlen); 107 client->addr = newaddr; 108 client->addrlen = addrlen; 109 110 return 0; 111 } 112 113 int http_client_set_method(HttpClient *client, const char *method) { 114 return http_client_set_method_len(client, method, method ? strlen(method) : 0); 115 } 116 117 int http_client_set_uri(HttpClient *client, const char *uri) { 118 return http_client_set_uri_len(client, uri, uri ? strlen(uri) : 0); 119 } 120 121 static int client_set_str(char **ptr, const char *str, size_t len) { 122 free(*ptr); 123 if(str) { 124 char *newvalue = malloc(len+1); 125 if(!newvalue) { 126 *ptr = NULL; 127 return 1; 128 } 129 memcpy(newvalue, str, len); 130 newvalue[len] = 0; 131 *ptr = newvalue; 132 } else { 133 *ptr = NULL; 134 } 135 return 0; 136 } 137 138 int http_client_set_method_len(HttpClient *client, const char *method, size_t len) { 139 return client_set_str(&client->method, method, len); 140 } 141 142 int http_client_set_uri_len(HttpClient *client, const char *uri, size_t len) { 143 return client_set_str(&client->uri, uri, len); 144 } 145 146 int http_client_add_request_header(HttpClient *client, cxmutstr name, cxmutstr value) { 147 return header_array_add(client->request_headers, name, value); 148 } 149 150 int http_client_add_request_header_copy(HttpClient *client, cxstring name, cxstring value) { 151 if(!client->mp) { 152 client->mp = cxMempoolCreate(64, CX_MEMPOOL_TYPE_PURE); 153 if(!client->mp) { 154 return 1; 155 } 156 } 157 158 cxmutstr n = cx_strdup_a(client->mp->allocator, name); 159 cxmutstr v = cx_strdup_a(client->mp->allocator, value); 160 161 int err = 1; 162 if(n.ptr && v.ptr) { 163 err = http_client_add_request_header(client, n, v); 164 } 165 if(err) { 166 cxFree(client->mp->allocator, n.ptr); 167 cxFree(client->mp->allocator, v.ptr); 168 } 169 return err; 170 } 171 172 int http_client_set_content_length(HttpClient *client, int64_t contentlength) { 173 client->req_content_length = contentlength; 174 char ctlen_buf[32]; 175 size_t len = snprintf(ctlen_buf, 32, "%" PRId64, contentlength); 176 return http_client_add_request_header_copy(client, cx_str("content-length"), cx_strn(ctlen_buf, len)); 177 } 178 179 int http_client_enable_chunked_transfer_encoding(HttpClient *client) { 180 client->req_content_length = -1; 181 return http_client_add_request_header(client, cx_mutstr("transfer-encoding"), cx_mutstr("chunked")); 182 } 183 184 int http_client_start(HttpClient *client) { 185 int socketfd = socket(AF_INET, SOCK_STREAM, 0); 186 if(socketfd < 0) { 187 return 1; 188 } 189 190 if(util_socket_setnonblock(socketfd, 1)) { 191 close(socketfd); 192 return 1; 193 } 194 195 client->socketfd = socketfd; 196 197 client->writeev.cookie = client; 198 client->writeev.fn = client_connected; 199 200 int ret = 1; 201 if(connect(socketfd, client->addr, client->addrlen)) { 202 int err = errno; 203 if(err == EINPROGRESS) { 204 ret = ev_pollout(client->ev, socketfd, &client->writeev); 205 } else { 206 log_ereport(LOG_FAILURE, "http-client-start: connect failed: %s", strerror(err)); 207 } 208 } else { 209 // TODO: call client_connected directly 210 } 211 212 if(ret) { 213 close(socketfd); 214 } 215 return ret; 216 } 217 218 static int create_req_buffer(HttpClient *client) { 219 CxBuffer buf; 220 if(cxBufferInit(&buf, cxDefaultAllocator, NULL, HTTP_CLIENT_BUFFER_SIZE, CX_BUFFER_AUTO_EXTEND)) { 221 return 1; 222 } 223 224 if(client->method) { 225 cxBufferPutString(&buf, "GET "); 226 } else { 227 cxBufferPutString(&buf, client->method); 228 } 229 cxBufferPutString(&buf, client->uri ? client->uri : "/"); 230 cxBufferPutString(&buf, " HTTP/1.1\r\n"); 231 232 HeaderArray *hdr = client->request_headers; 233 while(hdr) { 234 for(int i=0;i<hdr->len;i++) { 235 cxBufferPutString(&buf, hdr->headers[i].name); 236 cxBufferPutString(&buf, ": "); 237 cxBufferPutString(&buf, hdr->headers[i].value); 238 cxBufferPutString(&buf, "\r\n"); 239 } 240 hdr = hdr->next; 241 } 242 cxBufferPutString(&buf, "\r\n"); 243 client->transfer_buffer = buf.space; 244 client->transfer_buffer_alloc = buf.capacity; 245 client->transfer_buffer_len = buf.size; 246 247 return 0; 248 } 249 250 static int client_connected(EventHandler *ev, Event *event) { 251 HttpClient *client = event->cookie; 252 if(create_req_buffer(client)) { 253 // TODO: set error 254 return 0; // end 255 } 256 event->fn = client_io; 257 258 return client_io(ev, event); 259 } 260 261 static int client_io(EventHandler *ev, Event *event) { 262 HttpClient *client = event->cookie; 263 if(client->transfer_buffer_pos < client->transfer_buffer_len) { 264 if(client_send_request(client)) { 265 return client->error == 0; 266 } 267 } 268 269 // do we need to send a request body? 270 if(client->req_content_length != 0) { 271 if(client_send_request_body(client)) { 272 return client->error == 0; 273 } 274 } 275 276 // writing complete, switch to read events 277 event->events = EVENT_POLLIN; 278 279 if(client_read_response_header(client)) { 280 return client->error == 0; 281 } 282 if(client_read_response_body(client)) { 283 return client->error == 0; 284 } 285 286 return 0; 287 } 288 289 static int client_finished(EventHandler *ev, Event *event) { 290 HttpClient *client = event->cookie; 291 292 close(client->socketfd); 293 client->socketfd = -1; 294 295 // request finished 296 if(client->response_finished) { 297 client->response_finished(client, client->response_finished_userdata); 298 } 299 300 return 0; 301 } 302 303 static int client_send_request(HttpClient *client) { 304 size_t nbytes = client->transfer_buffer_len - client->transfer_buffer_pos; 305 ssize_t w; 306 while((w = write(client->socketfd, client->transfer_buffer + client->transfer_buffer_pos, nbytes)) > 0) { 307 client->transfer_buffer_pos += w; 308 nbytes = client->transfer_buffer_len - client->transfer_buffer_pos; 309 if(nbytes == 0) { 310 break; 311 } 312 } 313 314 if(w <= 0) { 315 if(errno != EAGAIN) { 316 // TODO: log correct host 317 log_ereport(LOG_VERBOSE, "http-client %s - %s: write failed: %s", "localhost", client->uri, strerror(errno)); 318 client->error = 1; 319 } 320 return 1; 321 } 322 323 return client->transfer_buffer_pos < client->transfer_buffer_len; 324 } 325 326 static int client_send_request_body(HttpClient *client) { 327 size_t rbody_readsize = client->transfer_buffer_alloc; 328 size_t rbody_buf_offset = 0; 329 if(client->req_content_length == -1) { 330 // chunked transfer encoding: 331 // don't fill req_buffer completely, reserve some space for 332 // a chunk header, that will be inserted at the beginning 333 rbody_readsize -= 16; 334 rbody_buf_offset = 16; 335 } 336 while(!client->request_body_complete) { 337 ssize_t r = client->request_body_read(client, client->transfer_buffer + rbody_buf_offset, rbody_readsize, client->request_body_read_userdata); 338 if(r <= 0) { 339 if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { 340 return 1; 341 } else if(r == 0) { 342 // EOF 343 client->request_body_complete = 1; 344 break; 345 } else { 346 // error 347 client->error = 1; 348 return 1; 349 } 350 } else if(client->req_content_length == -1 && r + 32 < rbody_readsize) { 351 // is it time to terminate the request body? 352 // try read some additional bytes, if it returns 0, we know 353 // the request body is complete and we can add the termination chunk 354 char *r2buf = client->transfer_buffer + rbody_buf_offset + r; 355 ssize_t r2 = client->request_body_read(client, r2buf, 32, client->request_body_read_userdata); 356 if(r > 0) { 357 r += r2; 358 } else if(r == 0) { 359 memcpy(r2buf, "0\r\n\r\n", 5); 360 r += 5; 361 client->request_body_complete = 1; 362 client->request_body_terminated = 1; 363 } else if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { 364 return 1; 365 } else { 366 client->error = 1; 367 return 1; 368 } 369 } 370 371 size_t startpos = 0; 372 if(client->req_content_length == -1) { 373 char chunkheader[16]; 374 int chunkheaderlen = snprintf(chunkheader, 16, "%zx\r\n", (size_t)r); 375 startpos = 16 - chunkheaderlen; 376 memcpy(client->transfer_buffer + startpos, chunkheader, chunkheaderlen); 377 } 378 379 client->req_contentlength_pos += r; 380 client->transfer_buffer_pos = startpos; 381 client->transfer_buffer_len = rbody_buf_offset + r; 382 if(client_send_request(client)) { 383 return 1; 384 } 385 } 386 387 // chunked transfer encoding: terminate 388 if(client->req_content_length == -1 && !client->request_body_terminated) { 389 memcpy(client->transfer_buffer, "0\r\n\r\n", 5); 390 client->transfer_buffer_pos = 0; 391 client->transfer_buffer_len = 5; 392 client->request_body_terminated = 1; 393 if(client_send_request(client)) { 394 return 1; 395 } 396 397 } else if(client->req_content_length != client->req_contentlength_pos) { 398 // incomplete request body 399 client->error = 1; 400 return 1; 401 } 402 403 return 0; 404 } 405 406 static int client_read_response_header(HttpClient *client) { 407 if(client->response_header_complete) { 408 return 0; 409 } 410 411 char *buffer = client->buffer.inbuf + client->buffer.pos; 412 size_t nbytes = client->buffer.maxsize - client->buffer.cursize; 413 414 ssize_t r; 415 while((r = read(client->socketfd, buffer, nbytes)) > 0) { 416 client->buffer.cursize += r; 417 if(!client->response_header_complete) { 418 switch(http_parser_process(client->parser)) { 419 case 0: { // finish 420 if(!http_parser_validate(client->parser)) { 421 client->error = 1; 422 return 1; 423 } 424 client->statuscode = client->parser->status; 425 426 client->response_header_complete = 1; 427 if(client->response_start) { 428 cxmutstr msg = client->parser->msg; 429 char t = msg.ptr[msg.length]; 430 msg.ptr[msg.length] = 0; 431 int ret = client->response_start(client, client->statuscode, msg.ptr, client->response_start_userdata); 432 msg.ptr[msg.length] = t; 433 434 // TODO: check ret 435 } 436 break; 437 } 438 case 1: { // need more data 439 continue; 440 } 441 case 2: { // error 442 client->error = 1; 443 return 1; 444 } 445 } 446 } 447 448 // header complete 449 break; 450 } 451 452 if(r <= 0) { 453 if(r == 0) { 454 // unexpected EOF 455 client->error = 1; 456 } else if(errno != EAGAIN) { 457 log_ereport(LOG_FAILURE, "http-client: IO error: %s", strerror(errno)); 458 client->error = 1; 459 } 460 return 1; 461 } 462 463 // initialize httpstream 464 HeaderArray *headers = client->parser->headers; 465 long long contentlength = 0; 466 int chunkedtransferenc = 0; 467 while(headers) { 468 for(int i=0;i<headers->len;i++) { 469 if(!cx_strcasecmp(headers->headers[i].name, "content-length")) { 470 if(!cx_strtoll(headers->headers[i].value, &contentlength, 10)) { 471 headers = NULL; 472 break; 473 } 474 } else if(!cx_strcasecmp(headers->headers[i].name, "transfer-encoding")) { 475 if(!cx_strcmp(headers->headers[i].value, "chunked")) { 476 chunkedtransferenc = 1; 477 headers = NULL; 478 break; 479 } 480 } 481 } 482 483 if(headers) { 484 headers = headers->next; 485 } 486 } 487 488 if(contentlength > 0 || chunkedtransferenc) { 489 IOStream *fd = Sysstream_new(NULL, client->socketfd); 490 if(!fd) { 491 client->error = 1; 492 return 1; 493 } 494 HttpStream *http = (HttpStream*)httpstream_new(NULL, fd); 495 if(!http) { 496 fd->free(fd); 497 } 498 if(contentlength > 0) { 499 http->max_read = contentlength; 500 httpstream_enable_buffered_read(&http->st, (char*)client->buffer.inbuf, client->buffer.maxsize, &client->buffer.cursize, &client->buffer.pos); 501 } else if(chunkedtransferenc) { 502 httpstream_enable_chunked_read(&http->st, (char*)client->buffer.inbuf, client->buffer.maxsize, &client->buffer.cursize, &client->buffer.pos); 503 } 504 client->stream = http; 505 } 506 507 return 0; 508 } 509 510 static int client_read_response_body(HttpClient *client) { 511 if(!client->stream) { 512 return 0; // no input stream -> no response body 513 } 514 515 char *buf = client->transfer_buffer; 516 size_t nbytes = client->transfer_buffer_alloc; 517 518 ssize_t r; 519 while((r = net_read(&client->stream->st, buf, nbytes)) > 0) { 520 if(client->response_body_write) { 521 int ret = client->response_body_write(client, buf, r, client->response_body_write_userdata); 522 // TODO: check ret 523 } 524 } 525 526 if(r < 0) { 527 if(r != HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { 528 client->error; 529 } 530 return 1; 531 } 532 533 return 0; 534 } 535 536 /* --------------------------------- Tests --------------------------------- */ 537 538 static CX_TEST(test_http_client_send_request) { 539 CX_TEST_DO { 540 EventHandler dummy; 541 HttpClient *client = http_client_new(&dummy); 542 543 int fds[2]; 544 util_socketpair(fds); 545 util_socket_setnonblock(fds[0], 1); 546 util_socket_setnonblock(fds[1], 1); 547 client->socketfd = fds[0]; 548 int sock = fds[1]; 549 550 // create a large test buffer, that is bigger than the socket buffer 551 // 32mb should be enough 552 size_t len = 32*1024*1024; 553 char *str = malloc(len); 554 // init the buffer with random data 555 for(size_t i=0;i<len;i+=sizeof(int)) { 556 int *p = (int*)(str+i); 557 *p = rand(); 558 } 559 560 client->transfer_buffer = str; 561 client->transfer_buffer_len = len; 562 563 // test client_send_request 564 565 int ret = client_send_request(client); 566 // It is very likely that the first client_send_request call doesn't 567 // fully write the request buffer to the socket 568 // In that case it returns 1 but without the error flag 569 CX_TEST_ASSERT(ret == 1 && !client->error); 570 CX_TEST_ASSERT(client->transfer_buffer_pos > 0); 571 CX_TEST_ASSERT(client->transfer_buffer_pos < len); 572 573 // read the request buffer from sock and continue with client_send_request 574 CxBuffer buf; 575 cxBufferInit(&buf, cxDefaultAllocator, NULL, len, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 576 char tmpbuf[1024]; 577 int writes = 1; 578 while(client->transfer_buffer_pos < client->transfer_buffer_len && writes < 2000000) { 579 ssize_t r = read(sock, tmpbuf, 1024); 580 CX_TEST_ASSERT(r >= 0); 581 cxBufferWrite(tmpbuf, 1, r, &buf); 582 ret = client_send_request(client); 583 CX_TEST_ASSERT(ret == 0 || (ret == 1 && !client->error)); 584 585 writes++; 586 } 587 CX_TEST_ASSERT(client->transfer_buffer_pos == client->transfer_buffer_len); 588 589 // finish reading the request buffer from sock 590 ssize_t r; 591 while((r = read(sock, tmpbuf, 1024)) > 0 && writes < 2000000) { 592 cxBufferWrite(tmpbuf, 1, r, &buf); 593 writes++; 594 } 595 596 CX_TEST_ASSERT(buf.size == len); 597 CX_TEST_ASSERT(!memcmp(str, buf.space, len)); 598 599 // cleanup 600 close(fds[0]); 601 close(fds[1]); 602 http_client_free(client); 603 cxBufferDestroy(&buf); 604 } 605 } 606 607 typedef struct TestResponse { 608 int status; 609 char *msg; 610 CxBuffer *response; 611 } TestResponse; 612 613 static int test_response_start(HttpClient *client, int status, char *msg, void *userdata) { 614 TestResponse *test = userdata; 615 test->status = status; 616 test->msg = strdup(msg); 617 return 0; 618 } 619 620 static ssize_t test_response_body_write(HttpClient *client, void *buf, size_t size, void *userdata) { 621 TestResponse *test = userdata; 622 cxBufferWrite(buf, 1, size, test->response); 623 return size; 624 } 625 626 627 typedef struct TestRequestBody { 628 char *content; 629 size_t length; 630 size_t pos; 631 int chunksize; 632 int max_reads; // max number of reads until test_request_body_read returns 0 633 int cur_reads; // current number of read-attempts 634 } TestRequestBody; 635 636 static ssize_t test_request_body_read(HttpClient *client, void *buf, size_t size, void *userdata) { 637 TestRequestBody *req = userdata; 638 req->cur_reads++; 639 if(req->chunksize == 0 || req->cur_reads > req->max_reads) { 640 return -1; 641 } 642 size_t max = req->length - req->pos; 643 if(max == 0) { 644 return 0; 645 } 646 647 size_t sz = req->chunksize > size ? size : req->chunksize; 648 if(sz > max) { 649 sz = max; 650 } 651 memcpy(buf, req->content + req->pos, sz); 652 req->pos += sz; 653 return sz; 654 } 655 656 static CX_TEST(test_http_client_send_request_body_chunked) { 657 CX_TEST_DO { 658 EventHandler dummy; 659 HttpClient *client = http_client_new(&dummy); 660 create_req_buffer(client); 661 client->req_content_length = -1; 662 663 int fds[2]; 664 util_socketpair(fds); 665 util_socket_setnonblock(fds[0], 1); 666 util_socket_setnonblock(fds[1], 1); 667 client->socketfd = fds[0]; 668 int sock = fds[1]; 669 670 // response buffer 671 CxBuffer buf; 672 cxBufferInit(&buf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 673 674 // test 675 char request_body[1024]; 676 memset(request_body, 'x', 1024); 677 memset(request_body+128, 'y', 128); 678 memset(request_body+384, 'z', 128); 679 memset(request_body+640, ':', 128); 680 memset(request_body+896, '!', 128); 681 682 TestRequestBody req; 683 req.content = request_body; 684 req.length = 1024; 685 req.pos = 0; 686 req.chunksize = 16; 687 req.max_reads = 8; 688 req.cur_reads = 0; 689 client->request_body_read = test_request_body_read; 690 client->request_body_read_userdata = &req; 691 692 memset(client->transfer_buffer, '_', client->transfer_buffer_alloc); 693 client->transfer_buffer_pos = 0; 694 client->transfer_buffer_len = 0; 695 696 // send the first 128 bytes 697 while(req.cur_reads <= req.max_reads) { 698 int ret = client_send_request_body(client); 699 CX_TEST_ASSERT(ret == 1); 700 CX_TEST_ASSERT(!client->error); 701 char buf2[1024]; 702 ssize_t r = read(sock, buf2, 1024); 703 if(r > 0) { 704 cxBufferWrite(buf2, 1, r, &buf); 705 } 706 } 707 708 // because we are using chunked transfer encoding, the result buffer 709 // (buf) should contain more than 128 bytes (additional chunk headers) 710 CX_TEST_ASSERT(buf.pos > 128); 711 712 // change chunk size to 128 713 req.max_reads = 9999; 714 req.chunksize = 128; 715 while(req.cur_reads <= req.max_reads) { 716 int ret = client_send_request_body(client); 717 CX_TEST_ASSERT(!client->error); 718 char buf2[2048]; 719 ssize_t r; 720 while((r = read(sock, buf2, 2048)) > 0) { 721 cxBufferWrite(buf2, 1, r, &buf); 722 } 723 if(ret == 0) { 724 break; 725 } 726 } 727 CX_TEST_ASSERT(req.cur_reads < req.max_reads); 728 729 // verify chunks 730 char test_request_body[1024]; 731 memset(test_request_body, 0, 1024); 732 733 int pos = 0; 734 int chunklen = 0; 735 char *str = buf.space; 736 while(str < buf.space + buf.size) { 737 cxstring chunkheader = cx_strn(str, 2); 738 if(!cx_strcmp(chunkheader, "0\r")) { 739 chunkheader.length = 1; 740 } 741 int ret = cx_strtoi(chunkheader, &chunklen, 16); 742 CX_TEST_ASSERT(ret == 0); 743 if(chunklen == 0) { 744 break; 745 } 746 747 char *data = str + 4; 748 CX_TEST_ASSERT(data + chunklen < buf.space + buf.size); 749 memcpy(test_request_body + pos, data, chunklen); 750 pos += chunklen; 751 str = data + chunklen; 752 } 753 CX_TEST_ASSERT(!memcmp(request_body, test_request_body, 1024)); 754 755 // cleanup 756 close(fds[0]); 757 close(fds[1]); 758 http_client_free(client); 759 cxBufferDestroy(&buf); 760 } 761 } 762 763 static CX_TEST_SUBROUTINE(test_read_response, cxstring response_str, CxBuffer *response_body) { 764 EventHandler dummy; 765 HttpClient *client = http_client_new(&dummy); 766 create_req_buffer(client); 767 client->req_content_length = -1; 768 769 int fds[2]; 770 util_socketpair(fds); 771 util_socket_setnonblock(fds[0], 1); 772 util_socket_setnonblock(fds[1], 1); 773 client->socketfd = fds[0]; 774 int sock = fds[1]; 775 776 TestResponse testr = { 0 }; 777 testr.response = response_body; 778 client->response_body_write = test_response_body_write; 779 client->response_body_write_userdata = &testr; 780 781 // test 782 783 size_t response_pos = 0; 784 while(response_pos < response_str.length) { 785 size_t nbytes = response_str.length - response_pos; 786 ssize_t w = write(sock, response_str.ptr + response_pos, nbytes); 787 if(w > 0) { 788 response_pos += w; 789 } 790 791 if(!client->response_header_complete) { 792 int ret = client_read_response_header(client); 793 CX_TEST_ASSERT(client->error == 0); 794 if(ret == 1) { 795 continue; 796 } 797 } 798 799 if(response_body != NULL) { 800 CX_TEST_ASSERT(client->stream != NULL); 801 802 int ret = client_read_response_body(client); 803 CX_TEST_ASSERT(client->error == 0); 804 if(ret == 1) { 805 continue; 806 } 807 } else { 808 CX_TEST_ASSERT(client->stream == NULL); 809 } 810 811 break; 812 } 813 814 // cleanup 815 close(fds[0]); 816 close(fds[1]); 817 http_client_free(client); 818 } 819 820 static CX_TEST(test_http_client_read_response_head) { 821 CX_TEST_DO { 822 char *response_str = 823 "HTTP/1.1 204 OK\r\n" 824 "Host: localhost\r\n" 825 "Content-length: 0\r\n" 826 "\r\n"; 827 828 CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), NULL); 829 830 response_str = 831 "HTTP/1.1 204 OK\r\n" 832 "Host: localhost\r\n" 833 "\r\n"; 834 835 CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), NULL); 836 } 837 } 838 839 static CX_TEST(test_http_client_read_response_ctlen) { 840 CX_TEST_DO { 841 char *response_str = 842 "HTTP/1.1 200 OK\r\n" 843 "Host: localhost\r\n" 844 "Content-length: 13\r\n" 845 "\r\n" 846 "Hello World!\n"; 847 CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 848 849 CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), buf); 850 CX_TEST_ASSERT(buf->size == 13); 851 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n")); 852 853 cxBufferFree(buf); 854 } 855 } 856 857 static CX_TEST(test_http_client_read_response_ctlen_big) { 858 CX_TEST_DO { 859 // create response body 860 size_t len = 1024*1024*32; 861 char *response_str = malloc(len); 862 char *str = response_str; 863 for(size_t i=0;i<len;i+=sizeof(int)) { 864 int *p = (int*)(str+i); 865 *p = rand(); 866 } 867 cxstring body = cx_strn(response_str, len); 868 869 // create request string 870 CxBuffer *req = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 871 cxBufferPutString(req, 872 "HTTP/1.1 200 OK\r\n" 873 "Host: localhost\r\n" 874 "Content-length: "); 875 char ctlen[32]; 876 snprintf(ctlen, 32, "%d\r\n\r\n", (int)len); 877 cxBufferPutString(req, ctlen); 878 cxBufferPutString(req, body); 879 cxBufferTerminate(req); 880 881 // response buffer 882 CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 883 884 // test 885 CX_TEST_CALL_SUBROUTINE(test_read_response, cx_strn(req->space, req->size), buf); 886 CX_TEST_ASSERT(buf->size == len); 887 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), body)); 888 889 cxBufferFree(req); 890 cxBufferFree(buf); 891 } 892 } 893 894 static CX_TEST_SUBROUTINE(test_http_client_io, const char *response_str, int status_code, const char *msg, CxBuffer *out_buf, size_t write_blocksz) { 895 EventHandler dummy; 896 HttpClient *client = http_client_new(&dummy); 897 898 int fds[2]; 899 util_socketpair(fds); 900 util_socket_setnonblock(fds[0], 1); 901 util_socket_setnonblock(fds[1], 1); 902 client->socketfd = fds[0]; 903 int sock = fds[1]; 904 905 // setup client 906 http_client_set_uri(client, "/test/uri/"); 907 http_client_set_method(client, "GET"); 908 http_client_add_request_header(client, cx_mutstr("Host"), cx_mutstr("localhost")); 909 http_client_add_request_header(client, cx_mutstr("Test1"), cx_mutstr("value1")); 910 http_client_add_request_header(client, cx_mutstr("Test2"), cx_mutstr("value2")); 911 create_req_buffer(client); 912 913 size_t req_header_len = client->transfer_buffer_len; 914 915 TestResponse testr = { 0 }; 916 testr.response = out_buf; 917 client->response_start = test_response_start; 918 client->response_start_userdata = &testr; 919 client->response_body_write = test_response_body_write; 920 client->response_body_write_userdata = &testr; 921 922 // test IO 923 Event event; 924 event.cookie = client; 925 int ret = client_io(&dummy, &event); 926 CX_TEST_ASSERT(!client->error); 927 CX_TEST_ASSERT(ret == 1); 928 929 // do IO and read request until the header is processed 930 size_t req_header_pos = 0; 931 char req_buf[4]; 932 while(req_header_pos < req_header_len) { 933 ssize_t r = read(sock, req_buf, 4); 934 if(r == 0) { 935 break; 936 } 937 CX_TEST_ASSERT(r > 0); 938 req_header_pos += r; 939 ret = client_io(&dummy, &event); 940 CX_TEST_ASSERT(!client->error); 941 CX_TEST_ASSERT(ret == 1); 942 } 943 CX_TEST_ASSERT(req_header_pos == req_header_len); 944 945 size_t response_str_len = strlen(response_str); 946 size_t response_str_pos = 0; 947 948 // send response and do IO 949 while(response_str_pos < response_str_len) { 950 size_t len = response_str_len - response_str_pos; 951 if(len > write_blocksz) { 952 len = write_blocksz; 953 } 954 ssize_t w = write(sock, response_str + response_str_pos, len); 955 if(w == 0) { 956 break; 957 } 958 CX_TEST_ASSERT(w > 0); 959 response_str_pos += w; 960 961 ret = client_io(&dummy, &event); 962 963 CX_TEST_ASSERT(!client->error); 964 } 965 CX_TEST_ASSERT(response_str_pos == response_str_len); 966 CX_TEST_ASSERT(testr.status == status_code); 967 CX_TEST_ASSERT(testr.msg); 968 CX_TEST_ASSERT(!strcmp(testr.msg, msg)); 969 970 // cleanup 971 free(testr.msg); 972 close(fds[0]); 973 close(fds[1]); 974 http_client_free(client); 975 } 976 977 static CX_TEST_SUBROUTINE(test_http_client_io_simple, size_t blocksz) { 978 char *response_str = 979 "HTTP/1.1 200 OK\r\n" 980 "Host: localhost\r\n" 981 "Content-length: 13\r\n" 982 "\r\n" 983 "Hello World!\n"; 984 CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 985 986 CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz); 987 CX_TEST_ASSERT(buf->size == 13); 988 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n")); 989 990 cxBufferFree(buf); 991 } 992 993 static CX_TEST(test_http_client_io_simple_1b) { 994 CX_TEST_DO { 995 CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1); 996 } 997 } 998 999 static CX_TEST(test_http_client_io_simple_2b) { 1000 CX_TEST_DO { 1001 CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 2); 1002 } 1003 } 1004 1005 static CX_TEST(test_http_client_io_simple_3b) { 1006 CX_TEST_DO { 1007 CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 3); 1008 } 1009 } 1010 1011 static CX_TEST(test_http_client_io_simple_16b) { 1012 CX_TEST_DO { 1013 CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 16); 1014 } 1015 } 1016 1017 static CX_TEST(test_http_client_io_simple_512b) { 1018 CX_TEST_DO { 1019 CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 512); 1020 } 1021 } 1022 1023 static CX_TEST_SUBROUTINE(test_http_client_io_chunked_transfer, size_t blocksz) { 1024 char *response_str = 1025 "HTTP/1.1 200 OK\r\n" 1026 "Host: localhost\r\n" 1027 "Transfer-encoding: chunked\r\n" 1028 "\r\n" 1029 "d\r\n" 1030 "Hello World!\n" 1031 "\r\n" 1032 "0\r\n\r\n"; 1033 CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 1034 1035 CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz); 1036 CX_TEST_ASSERT(buf->size == 13); 1037 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n")); 1038 1039 cxBufferFree(buf); 1040 } 1041 1042 static CX_TEST(test_http_client_io_chunked_transfer_1b) { 1043 CX_TEST_DO { 1044 CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 10); 1045 } 1046 } 1047 1048 /* 1049 static CX_TEST(test_http_client_io_chunked_transfer_8b) { 1050 CX_TEST_DO { 1051 CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 16); 1052 } 1053 } 1054 1055 static CX_TEST(test_http_client_io_chunked_transfer_64b) { 1056 CX_TEST_DO { 1057 CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 64); 1058 } 1059 } 1060 */ 1061 1062 void http_client_add_tests(CxTestSuite *suite) { 1063 cx_test_register(suite, test_http_client_send_request); 1064 cx_test_register(suite, test_http_client_send_request_body_chunked); 1065 cx_test_register(suite, test_http_client_read_response_head); 1066 cx_test_register(suite, test_http_client_read_response_ctlen); 1067 cx_test_register(suite, test_http_client_read_response_ctlen_big); 1068 cx_test_register(suite, test_http_client_io_simple_1b); 1069 cx_test_register(suite, test_http_client_io_simple_2b); 1070 cx_test_register(suite, test_http_client_io_simple_3b); 1071 cx_test_register(suite, test_http_client_io_simple_16b); 1072 cx_test_register(suite, test_http_client_io_simple_512b); 1073 cx_test_register(suite, test_http_client_io_chunked_transfer_1b); 1074 } 1075