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 #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
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
254 return 0;
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
270 if(client->req_content_length !=
0) {
271 if(client_send_request_body(client)) {
272 return client->error ==
0;
273 }
274 }
275
276
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
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
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
331
332
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
343 client->request_body_complete =
1;
344 break;
345 }
else {
346
347 client->error =
1;
348 return 1;
349 }
350 }
else if(client->req_content_length ==
-1 && r +
32 < rbody_readsize) {
351
352
353
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
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
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: {
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
435 }
436 break;
437 }
438 case 1: {
439 continue;
440 }
441 case 2: {
442 client->error =
1;
443 return 1;
444 }
445 }
446 }
447
448
449 break;
450 }
451
452 if(r <=
0) {
453 if(r ==
0) {
454
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
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;
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
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
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
551
552 size_t len =
32*
1024*
1024;
553 char *str = malloc(len);
554
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
564
565 int ret = client_send_request(client);
566
567
568
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
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
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
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;
633 int cur_reads;
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
671 CxBuffer buf;
672 cxBufferInit(&buf, cxDefaultAllocator,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
673
674
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
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
709
710 CX_TEST_ASSERT(buf.pos >
128);
711
712
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
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
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
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
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
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
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
882 CxBuffer *buf = cxBufferCreate(
NULL,
NULL,
1024,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
883
884
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
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
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
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
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
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
1050
1051
1052
1053
1054
1055
1056
1057
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